#include "devices/shutdown.h"
#include <console.h>
#include <stdio.h>
#include "devices/kbd.h"
#include "devices/serial.h"
#include "devices/timer.h"
#include "threads/io.h"
#include "threads/thread.h"
#ifdef USERPROG
#include "userprog/exception.h"
#endif
#ifdef FILESYS
#include "devices/block.h"
#include "filesys/filesys.h"
#endif

/* Keyboard control register port. */
#define CONTROL_REG 0x64

/* How to shut down when shutdown() is called. */
static enum shutdown_type how = SHUTDOWN_NONE;

static void print_stats(void);

/* Shuts down the machine in the way configured by
   shutdown_configure().  If the shutdown type is SHUTDOWN_NONE
   (which is the default), returns without doing anything. */
void shutdown(void)
{
    switch (how)
    {
    case SHUTDOWN_POWER_OFF:
        shutdown_power_off();
        break;

    case SHUTDOWN_REBOOT:
        shutdown_reboot();
        break;

    default:
        /* Nothing to do. */
        break;
    }
}

/* Sets TYPE as the way that machine will shut down when Pintos
   execution is complete. */
void shutdown_configure(enum shutdown_type type)
{
    how = type;
}

/* Reboots the machine via the keyboard controller. */
void shutdown_reboot(void)
{
    printf("Rebooting...\n");

    /* See [kbd] for details on how to program the keyboard
     * controller. */
    for (;;)
    {
        int i;

        /* Poll keyboard controller's status byte until
         * 'input buffer empty' is reported. */
        for (i = 0; i < 0x10000; i++)
        {
            if ((inb(CONTROL_REG) & 0x02) == 0)
                break;
            timer_udelay(2);
        }

        timer_udelay(50);

        /* Pulse bit 0 of the output port P2 of the keyboard controller.
         * This will reset the CPU. */
        outb(CONTROL_REG, 0xfe);
        timer_udelay(50);
    }
}

/* Powers down the machine we're running on,
   as long as we're running on Bochs or QEMU. */
void shutdown_power_off(void)
{
    const char s[] = "Shutdown";
    const char *p;

#ifdef FILESYS
    filesys_done();
#endif

    print_stats();

    printf("Powering off...\n");
    serial_flush();

    /* ACPI power-off */
    outw(0xB004, 0x2000);

    /* This is a special power-off sequence supported by Bochs and
       QEMU, but not by physical hardware. */
    for (p = s; *p != '\0'; p++)
        outb(0x8900, *p);

    /* For newer versions of qemu, you must run with -device
     * isa-debug-exit, which exits on any write to an IO port (by
     * default 0x501).  Qemu's exit code is double the value plus one,
     * so there is no way to exit cleanly.  We use 0x31 which should
     * result in a qemu exit code of 0x63.  */
    outb(0x501, 0x31);

    /* This will power off a VMware VM if "gui.exitOnCLIHLT = TRUE"
       is set in its configuration file.  (The "pintos" script does
       that automatically.)  */
    asm volatile(
        "cli; hlt"
        :
        :
        : "memory");

    /* None of those worked. */
    printf("still running...\n");
    for (;;)
        ;
}

/* Print statistics about Pintos execution. */
static void print_stats(void)
{
    timer_print_stats();
    thread_print_stats();
#ifdef FILESYS
    block_print_stats();
#endif
    console_print_stats();
    kbd_print_stats();
#ifdef USERPROG
    exception_print_stats();
#endif
}
